#include "FFMediaPlayer.h"
#include "TarsThreadPool.h"

FFMediaPlayer::FFMediaPlayer() :
    ic(nullptr),
    abort(true),
    volume(SDL_MIX_MAXVOLUME),
    paused(false),
    muted(false),
    eof(true),
    continue_read_thread(nullptr),
    audio_buf1_size(0),
    swr_ctx(nullptr),
    audioq(make_shared<PacketQueue>())
{
    init();
}

FFMediaPlayer::~FFMediaPlayer()
{
    // 没有启动播放直接退出
    if(abort)
        return;

    // 停止播放
    stop();

    // 关闭流
    avformat_close_input(&ic);

    // 销毁packet队列
    audioq->packet_queue_destroy();

    // 销毁帧队列
    sampq.frame_queue_destory();

    // 释放信号量
    SDL_DestroyCond(continue_read_thread);
}

bool FFMediaPlayer::init()
{
    // SDL初始化标志 音频＋定时器
    int flags = SDL_INIT_AUDIO | SDL_INIT_TIMER;
    if (SDL_Init (flags)) {
        av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
        return false;
    }

    // 初始化包队列
    if (audioq->packet_queue_init() < 0){
        av_log(NULL, AV_LOG_FATAL, "frame_queue_init() fail\n");
        return false;
    }

    // 初始化帧队列
    if (sampq.frame_queue_init(audioq, SAMPLE_QUEUE_SIZE, true) < 0){
        av_log(NULL, AV_LOG_FATAL, "frame_queue_init() fail\n");
        return false;
    }

    // 创建读线程信号量
    if (!(continue_read_thread = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return false;
    }

    av_log(NULL, AV_LOG_INFO, "FFMediaPlayer init OK\n");
    return true;
}

bool FFMediaPlayer::play(const char *path)
{
    if(not path){
        av_log(NULL, AV_LOG_ERROR, "filename is NULL\n");
        return false;
    }

    // 切歌->如果当时在播放则停止播放
    if(not abort)
        stop();

    // 保存文件名
    fileName = path;

    // 参数初始化
    eof = false;        // 完结标志
    abort = false;      // 中止标志
    audioIndex = -1;    // 音频流索引

    // 创建读线程
    rFuture = TarsThreadPool::getInstance().exec(bind(&FFMediaPlayer::readThread, this));
    return true;
}

void FFMediaPlayer::stop()
{
    // 请求退出
    abort = true;

    // 等待读线程执行完毕
    if(rFuture.valid())
        cout << rFuture.get() << endl;

    // 关闭流资源
    if(audioIndex >= 0)
        streamComponentClose(audioIndex);
}

int FFMediaPlayer::decodeInterruptCb(void *arg)
{
    FFMediaPlayer *is = (FFMediaPlayer*)arg;
    return is->abort;
}

void FFMediaPlayer::printError(const char *filename, int err)
{
    char errbuf[128];
    const char *ptr = errbuf;

    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
        ptr = strerror(AVUNERROR(err));
    av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, ptr);
}

int FFMediaPlayer::audio_decode_frame(FFMediaPlayer *is)
{
    int data_size;
    int resampled_data_size = 0;
    int64_t dec_channel_layout;
    FrameQueue::Frame *af;
    if (is->paused)	//暂停状态，返回-1，sdl_audio_callback会处理为输出静音
        return -1;

    //1. 从sampq取一帧，必要时丢帧
    do {
        if (!(af = is->sampq.frame_queue_peek_readable()))
            return -1;
        is->sampq.frame_queue_next();
    } while (af->serial != is->audioq->packet_queue_serial());

    //2. 计算这一帧的字节数
    data_size = av_samples_get_buffer_size(nullptr, af->frame->channels, af->frame->nb_samples,
                                           (enum AVSampleFormat)af->frame->format, 1);

    //[]计算dec_channel_layout，用于确认是否需要重新初始化重采样（难道af->channel_layout不可靠？不理解）
    dec_channel_layout =
        (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
        af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);

    //[]判断是否需要重新初始化重采样
    if (af->frame->format        != is->audio_src.fmt            ||
        dec_channel_layout       != is->audio_src.channel_layout ||
        af->frame->sample_rate   != is->audio_src.freq            ) {

        // 释放重采样器
        swr_free(&is->swr_ctx);
        is->swr_ctx = swr_alloc_set_opts(nullptr,
                                         is->audio_tgt.channel_layout,
                                         (enum AVSampleFormat)is->audio_tgt.fmt,
                                         is->audio_tgt.freq,
                                         dec_channel_layout,
                                         (enum AVSampleFormat)af->frame->format,
                                         af->frame->sample_rate,
                                         0, nullptr);
        if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
            av_log(NULL, AV_LOG_ERROR,
                   "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",
                    af->frame->sample_rate, av_get_sample_fmt_name((enum AVSampleFormat)af->frame->format), af->frame->channels,
                    is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);
            swr_free(&is->swr_ctx);
            return -1;
        }
        is->audio_src.channel_layout = dec_channel_layout;
        is->audio_src.channels       = af->frame->channels;
        is->audio_src.freq           = af->frame->sample_rate;
        is->audio_src.fmt            = (enum AVSampleFormat)af->frame->format;
    }

    //3. 获取这一帧的数据
    if (is->swr_ctx) {	//[]如果初始化了重采样，则对这一帧数据重采样输出
        // 重采样输入参数1：输入音频样本数是af->frame->nb_samples
        // 重采样输入参数2：输入音频缓冲区
        const uint8_t **in = (const uint8_t **)af->frame->extended_data;
        // 重采样输出参数1：输出音频缓冲区
        uint8_t **out = &is->audio_buf1;
        int out_count = af->frame->nb_samples + 256;
        int out_size  = av_samples_get_buffer_size(nullptr, is->audio_tgt.channels, out_count, is->audio_tgt.fmt, 0);
        int len2;
        if (out_size < 0) {
            av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n");
            return -1;
        }
        //如果out_size比is->audio_buf1_size大则根据out_size重新分配内存
        av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
        if (!is->audio_buf1)
            return AVERROR(ENOMEM);
        len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
        if (len2 < 0) {
            av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n");
            return -1;
        }
        if (len2 == out_count) {
            av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n");
            if (swr_init(is->swr_ctx) < 0)
                swr_free(&is->swr_ctx);
        }
        is->audio_buf = is->audio_buf1;
        resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
    } else {
        is->audio_buf = af->frame->data[0];
        resampled_data_size = data_size;
    }

    return resampled_data_size;
}

void FFMediaPlayer::sdlAudioCallback(void *opaque, uint8_t *stream, int len)
{
    FFMediaPlayer *is = (FFMediaPlayer*)opaque;
    int audio_size, len1;

    while (len > 0) {
        /* (1)如果is->audio_buf_index < is->audio_buf_size则说明上次拷贝还剩余一些数据，
         * 先拷贝到stream再调用audio_decode_frame
         * (2)如果audio_buf消耗完了，则调用audio_decode_frame重新填充audio_buf
         */
        if(is->audio_buf_index >= is->audio_buf_size){
            audio_size = audio_decode_frame(is);
            if (audio_size < 0) {
                is->audio_buf = nullptr;
                is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
            } else {
                is->audio_buf_size = audio_size;    //字节为单位
            }
            is->audio_buf_index = 0;
        }

        //根据缓冲区剩余大小量力而行
        len1 = is->audio_buf_size - is->audio_buf_index;
        if (len1 > len)
            len1 = len;
        //根据audio_volume决定如何输出audio_buf
        /* 判断是否为静音，以及当前音量的大小，如果音量为最大则直接拷贝数据 */
        if (!is->muted && is->audio_buf && is->volume == SDL_MIX_MAXVOLUME)
            memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        else {
            memset(stream, 0, len1);
            // 调整音量
            /* 如果处于mute状态则直接使用stream填0数据, 暂停时is->audio_buf= NULL */
            if (!is->muted && is->audio_buf)
                SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, AUDIO_S16SYS, len1, is->volume);
        }
        len -= len1;
        stream += len1;
        /* 更新is->audio_buf_index，指向audio_buf中未被拷贝到stream的数据（剩余数据）的起始位置 */
        is->audio_buf_index += len1;
    }
}

bool FFMediaPlayer::stream_has_enough_packets(AVStream *st, int stream_id, shared_ptr<PacketQueue> queue)
{
    return stream_id < 0 || // 没有该流
           queue->packet_queue_isAbort() || // 请求退出
           (st->disposition & AV_DISPOSITION_ATTACHED_PIC) || // 是attached_pic
           (queue->packet_queue_packet_num() > MIN_FRAMES  // packet数 > 25
            && (!queue->packet_queue_duration() || // 满足PacketQueue总时长为0
                av_q2d(st->time_base) * queue->packet_queue_duration() > 1.0)); // 或总时长超过1s
}

string FFMediaPlayer::readThread()
{
    int err;
    int ret = -1;
    AVPacket *pkt = nullptr;

    // 分配内存
    pkt = av_packet_alloc();
    if (!pkt) {
        av_log(NULL, AV_LOG_FATAL, "Could not allocate packet.\n");
        goto fail;
    }

    // 创建上下文结构体，这个结构体是最上层的结构体，表示输入上下文
    ic = avformat_alloc_context();
    if (!ic) {
        av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
        goto fail;
    }

    /* 设置中断回调函数，如果出错或者退出，就根据目前程序设置的状态选择继续check或者直接退出 */
    ic->interrupt_callback.callback = decodeInterruptCb;
    ic->interrupt_callback.opaque = this;

    /* 打开文件，主要是探测协议类型，如果是网络文件则创建网络链接等 */
    err = avformat_open_input(&ic, fileName.c_str(), nullptr, nullptr);
    if (err < 0) {
        printError(fileName.c_str(), err);
        goto fail;
    }

    // 获取流信息
    err = avformat_find_stream_info(ic, nullptr);
    if (err < 0) {
        av_log(NULL, AV_LOG_WARNING, "%s: could not find codec parameters\n", fileName.c_str());
        goto fail;
    }

    //打印媒体信息 参数为AVFormatContext上下文、流索引值（一般不用关心，直接写0）、文件名、是否是输出文件（1：是  0：不是）
    av_dump_format(ic, 0, fileName.c_str(), 0);

    //查找音频流对应的ID
    audioIndex = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, -1);
    if(audioIndex == -1)
    {
        av_log(NULL, AV_LOG_WARNING, "av_find_best_stream is not index\n");
        goto fail;
    }

    // 打开流
    if(streamComponentOpen(audioIndex) < 0){
        av_log(NULL, AV_LOG_WARNING, "streamComponentOpen is fail\n");
        goto fail;
    }

    // 获取流的总时长
    duration = ic->streams[audioIndex]->duration * av_q2d(ic->streams[audioIndex]->time_base);

    while (true) {
        // 检测是否退出
        if (abort)
            break;

        // 检测队列是否已经有足够的数据（缓存队列有足够的包，不需要继续读取数据）
        if(audioq->packet_queue_size() > MAX_QUEUE_SIZE
                || stream_has_enough_packets(ic->streams[audioIndex], audioIndex, audioq)) {
            /* wait 10 ms */
            SDL_CondWaitTimeout(continue_read_thread, nullptr, 10);
        }

        // 读取数据包
        ret = av_read_frame(ic, pkt);
        if(ret < 0) {
            if(ret == AVERROR_EOF && not eof){ // 读完了
                if(audioIndex >= 0) // 插入空包
                    audioq->packet_queue_put_nullpacket(pkt, audioIndex);
                eof = true;
            }
            /* wait 10 ms */
            SDL_CondWaitTimeout(continue_read_thread, nullptr, 10);
            continue;   // 继续循环 保证线程的运行，比如seek到某个位置播放可以继续响应
        } else {
            eof = false;
        }

        // 将音频数据送入queue中
        if(pkt->stream_index == audioIndex)
            audioq->packet_queue_put(pkt);
        else
            av_packet_unref(pkt);
    }

    // 正常退出
    ret = 0;

fail:
    eof = true;
    av_packet_free(&pkt);
    if(ret != 0) {
        if(ic)
            avformat_close_input(&ic);
        audioIndex = -1;
        abort = true;
        return "readThread Error exit";
    }
    return "readThread Normal exit";
}

string FFMediaPlayer::audioThread()
{
    // 分配解码帧
    AVFrame *frame = av_frame_alloc();
    FrameQueue::Frame *af;
    int got_frame = 0;	// 是否读取到帧
    AVRational tb;		// timebase

    if(!frame){
        av_log(NULL, AV_LOG_WARNING, "av_frame_alloc error\n");
        return "audioThread Error exit";
    }

    do{
        // 1.读解码帧
        if ((got_frame = auddec.decoder_decode_frame(frame)) < 0)
            goto end;

        // 获取到帧
        if (got_frame) {
            tb = (AVRational){1, frame->sample_rate}; // 设置sample_rate 为 timebase
            // 2. 获取可写Frame位置
            if (!(af = sampq.frame_queue_peek_writable()))
                goto end;
            // 3. 设置Frame并放入FrameQueue
            af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); // 转换为秒
            af->pos = frame->pkt_pos;
            af->serial = auddec.pkt_serial;
            af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate}); // 帧持续时间

            av_frame_move_ref(af->frame, frame);	// 转移
            sampq.frame_queue_push();               // 更新写索引
        }
    }while (true);

end:
   av_frame_free(&frame);
   return "audioThread Normal exit";
}

int FFMediaPlayer::streamComponentOpen(int streamIndex)
{
    int ret;
    AVCodecContext *avctx;  // 解码器上下文
    const AVCodec *codec;   // 解码器
    int sampleRate;         // 采样率
    int nbChannels;         // 通道数
    int64_t layout;         // 声道布局

    //创建一个codec context保存解码相关的上下文信息
    avctx = avcodec_alloc_context3(nullptr);
    if (!avctx){
        av_log(NULL, AV_LOG_WARNING, "avcodec_alloc_context3 error\n");
        return AVERROR(ENOMEM);
    }

    // 拷贝流信息到解码器上下文
    ret = avcodec_parameters_to_context(avctx, ic->streams[streamIndex]->codecpar);
    if (ret < 0) {
        av_log(NULL, AV_LOG_WARNING, "avcodec_parameters_to_context error\n");
        goto fail;
    }

    // 设置AVPacket的时基为 流的time_base
    avctx->pkt_timebase = ic->streams[streamIndex]->time_base;

    //寻找解码器
    codec = avcodec_find_decoder(avctx->codec_id);
    if(codec == nullptr)
    {
        av_log(NULL, AV_LOG_WARNING, "No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id));
        ret = AVERROR(EINVAL);
        goto fail;
    }

    // 打开解码器
    if ((ret = avcodec_open2(avctx, codec, nullptr)) < 0) {
        av_log(NULL, AV_LOG_WARNING, "avcodec_open2 error\n");
        goto fail;
    }

    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        sampleRate = avctx->sample_rate;
        nbChannels = avctx->channels;
        layout     = avctx->channel_layout;

        /* 准备音频输出 */
        if ((ret = audioOpen(this, layout, nbChannels, sampleRate, audio_tgt)) < 0)
            goto fail;
        audio_hw_buf_size = ret;
        audio_src = audio_tgt;
        audio_buf_size  = 0;
        audio_buf_index = 0;

        if((ret = auddec.init(avctx, audioq, continue_read_thread)) < 0)
            goto fail;

        // 创建音频解码线程
        aFuture = TarsThreadPool::getInstance().exec(bind(&FFMediaPlayer::audioThread, this));
        SDL_PauseAudioDevice(audioDev, 0);
        break;
    default:
        break;
    }

    return ret;
fail:
    avcodec_free_context(&avctx);
    return ret;
}

void FFMediaPlayer::streamComponentClose(int streamIndex)
{
    AVCodecParameters *codecpar;
    if (streamIndex < 0 || (uint32_t)streamIndex >= ic->nb_streams)
        return;

    // 获取解码器信息
    codecpar = ic->streams[streamIndex]->codecpar;
    switch (codecpar->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        // 中止队列
        audioq->packet_queue_abort();
        // 唤醒帧队列 让它退出
        sampq.frame_queue_signal();
        // 等待音频解码线程退出
        if(aFuture.valid())
            cout << aFuture.get() << endl;
        // 重置队列
        audioq->packet_queue_flush();
        // 关闭音频设备
        SDL_CloseAudioDevice(audioDev);
        // 关闭解码器
        auddec.destroy();
        // 释放重采样器
        swr_free(&swr_ctx);
        // 释放重采样时申请的堆空间
        av_freep(&audio_buf1);
        audio_buf1_size = 0;
        audio_buf = nullptr;
        break;
    default:
        break;
    }
}

int FFMediaPlayer::audioOpen(void *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, AudioParams &audio_hw_params)
{
    SDL_AudioSpec wanted_spec, spec;
    const char *env;
    static const int next_nb_channels[] = {0, 0, 1, 6, 2, 6, 4, 6};
    static const int next_sample_rates[] = {0, 44100, 48000, 96000, 192000};
    int next_sample_rate_idx = FF_ARRAY_ELEMS(next_sample_rates) - 1;

    env = SDL_getenv("SDL_AUDIO_CHANNELS");
    if (env) {
        wanted_nb_channels = atoi(env);
        wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
    }
    if (!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) {
        wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
        wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
    }
    wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout);
    wanted_spec.channels = wanted_nb_channels;
    wanted_spec.freq = wanted_sample_rate;
    if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) {
        av_log(NULL, AV_LOG_ERROR, "Invalid sample rate or channel count!\n");
        return -1;
    }
    while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq)
        next_sample_rate_idx--;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.silence = 0;
    wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
    wanted_spec.callback = sdlAudioCallback;
    wanted_spec.userdata = opaque;
    while (!(audioDev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE))) {
        av_log(NULL, AV_LOG_WARNING, "SDL_OpenAudio (%d channels, %d Hz): %s\n",
               wanted_spec.channels, wanted_spec.freq, SDL_GetError());
        wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];
        if (!wanted_spec.channels) {
            wanted_spec.freq = next_sample_rates[next_sample_rate_idx--];
            wanted_spec.channels = wanted_nb_channels;
            if (!wanted_spec.freq) {
                av_log(NULL, AV_LOG_ERROR,
                       "No more combinations to try, audio open failed\n");
                return -1;
            }
        }
        wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels);
    }
    if (spec.format != AUDIO_S16SYS) {
        av_log(NULL, AV_LOG_ERROR,
               "SDL advised audio format %d is not supported!\n", spec.format);
        return -1;
    }
    if (spec.channels != wanted_spec.channels) {
        wanted_channel_layout = av_get_default_channel_layout(spec.channels);
        if (!wanted_channel_layout) {
            av_log(NULL, AV_LOG_ERROR,
                   "SDL advised channel count %d is not supported!\n", spec.channels);
            return -1;
        }
    }

    audio_hw_params.fmt = AV_SAMPLE_FMT_S16;
    audio_hw_params.freq = spec.freq;
    audio_hw_params.channel_layout = wanted_channel_layout;
    audio_hw_params.channels =  spec.channels;
    audio_hw_params.frame_size = av_samples_get_buffer_size(NULL, audio_hw_params.channels, 1, audio_hw_params.fmt, 1);
    audio_hw_params.bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params.channels, audio_hw_params.freq, audio_hw_params.fmt, 1);
    if (audio_hw_params.bytes_per_sec <= 0 || audio_hw_params.frame_size <= 0) {
        av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size failed\n");
        return -1;
    }
    return spec.size;
}
